package org.karelun.rabbit.rcp.editor;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.emf.cdo.eresource.CDOResource;
import org.eclipse.emf.cdo.transaction.CDOTransactionConflictEvent;
import org.eclipse.emf.cdo.transaction.CDOTransactionFinishedEvent;
import org.eclipse.emf.cdo.transaction.CDOTransactionStartedEvent;
import org.eclipse.emf.cdo.util.CDOUtil;
import org.eclipse.emf.cdo.view.CDOView;
import org.eclipse.emf.cdo.view.CDOViewInvalidationEvent;
import org.eclipse.emf.common.command.BasicCommandStack;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.emf.common.command.CommandStackListener;
import org.eclipse.emf.common.ui.viewer.IViewerProvider;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.edit.EMFEditPlugin;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
import org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.net4j.util.event.IEvent;
import org.eclipse.net4j.util.event.IListener;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.EditorPart;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.eclipse.ui.views.properties.PropertySheet;
import org.eclipse.ui.views.properties.tabbed.ITabbedPropertySheetPageContributor;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
import org.karelun.rabbit.bridge.ITransactionEditingDomain;
import org.karelun.rabbit.bridge.ITransactionEditingDomainProvider;
import org.karelun.rabbit.bridge.TransactionAdapterFactoryEditingDomain;
import org.karelun.rabbit.bridge.ui.action.RabbitEditorActionBarContributor;
import org.karelun.rabbit.graph.provider.GraphItemProviderAdapterFactory;

public class EMFRabbitEditor extends EditorPart implements ITransactionEditingDomainProvider,ISelectionProvider,IViewerProvider,IMenuListener,ITabbedPropertySheetPageContributor{
	
	private CDOView cdoView;
	
	private ITransactionEditingDomain editingDomain;

	private ComposedAdapterFactory adapterFactory;
	
	private TreeViewer treeViewer;
	
	private IPropertySheetPage propertySheetPage;
	
	private Collection<ISelectionChangedListener> selectionChangedListeners = new ArrayList<ISelectionChangedListener>();
	
	private ISelection editorSelection=StructuredSelection.EMPTY;
	
	private IListener cdoViewListener=new IListener() {
		public void notifyEvent(IEvent event) {
			if(event instanceof CDOViewInvalidationEvent){
			}else if(event instanceof CDOTransactionStartedEvent){
				cdoViewDirtyStateChanged();
			}else if(event instanceof CDOTransactionFinishedEvent){
				refreshTreeViewer();
				cdoViewDirtyStateChanged();
			}else if(event instanceof CDOTransactionConflictEvent){
			}
		}
	};
	
	private IPartListener partListener=new IPartListener() {
		public void partOpened(IWorkbenchPart part) {
		}
		
		@Override
		public void partDeactivated(IWorkbenchPart part) {
		}
		
		@Override
		public void partClosed(IWorkbenchPart part) {
		}
		
		@Override
		public void partBroughtToTop(IWorkbenchPart part) {
		}
		
		@Override
		public void partActivated(IWorkbenchPart part) {
			if(part instanceof PropertySheet){
				if(((PropertySheet)part).getCurrentPage()==propertySheetPage){
					getActionBarContributor().setActiveEditor(EMFRabbitEditor.this);
					setSelection(getSelection());
				}
			}
		}
	};
	
	public EMFRabbitEditor() {
		initializeEditingDomain();
	}
	
	private void initializeEditingDomain()
	  {
	    ComposedAdapterFactory.Descriptor.Registry registry = EMFEditPlugin.getComposedAdapterFactoryDescriptorRegistry();
	    adapterFactory = new ComposedAdapterFactory(registry);
	    adapterFactory.addAdapterFactory(new ResourceItemProviderAdapterFactory());
	    adapterFactory.addAdapterFactory(new GraphItemProviderAdapterFactory());
	    adapterFactory.addAdapterFactory(new ReflectiveItemProviderAdapterFactory());
	  }
	
	@Override
	public void init(IEditorSite site, IEditorInput input)
			throws PartInitException {
		setSite(site);
	    site.setSelectionProvider(this);
	    setInputWithNotify(input);
	    site.getPage().addPartListener(partListener);
	}
	
	@Override
	public void createPartControl(Composite parent) {
		createModel();
		Composite composite = new Composite(parent, SWT.NONE);
		composite.setLayout(new GridLayout());
		composite.setLayoutData(new GridData(GridData.FILL_BOTH));
		treeViewer=new TreeViewer(composite, SWT.MULTI);
		treeViewer.getControl().setLayoutData(new GridData(GridData.FILL_BOTH));
		treeViewer.setContentProvider(new AdapterFactoryContentProvider(adapterFactory));
		treeViewer.setLabelProvider(new AdapterFactoryLabelProvider(adapterFactory));
		treeViewer.setInput(editingDomain.getResourceSet());
		treeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
			public void selectionChanged(SelectionChangedEvent event) {
				setSelection(event.getSelection());
			}
		});
		createContextMenu();
	}

	private void createModel() {
		IRabbitEditorInput editorInput=(IRabbitEditorInput)getEditorInput();
		cdoView=editorInput.getCDOView();
		BasicCommandStack commandStack = new BasicCommandStack();
		commandStack.addCommandStackListener(new CommandStackListener(){
			public void commandStackChanged(final EventObject event) {
				getSite().getShell().getDisplay().asyncExec(new Runnable(){
					public void run() {
						Command mostRecentCommand = ((CommandStack)event.getSource()).getMostRecentCommand();
						if(mostRecentCommand!=null){
							Collection<?> affectedObjects = mostRecentCommand.getAffectedObjects();
							StructuredSelection ss = new StructuredSelection(affectedObjects.toArray());
							if(treeViewer!=null){
								treeViewer.setSelection(ss, true);
							}
							setSelectionToViewer(affectedObjects);
						}
						if(propertySheetPage!=null&&!propertySheetPage.getControl().isDisposed()){
//							propertySheetPage.refresh();
						}
					}
				});
			}
		});
		editingDomain=new TransactionAdapterFactoryEditingDomain(adapterFactory, commandStack,cdoView.getResourceSet(),cdoView);
		cdoView.addListener(cdoViewListener);
	}
	
	private void createContextMenu() {
		MenuManager contextMenu = new MenuManager("#PopUp");
		contextMenu.add(new Separator("additions"));
		contextMenu.setRemoveAllWhenShown(true);
		contextMenu.addMenuListener(this);
		Menu menu= contextMenu.createContextMenu(treeViewer.getControl());
		treeViewer.getControl().setMenu(menu);
	}

	@Override
	public void doSave(IProgressMonitor monitor) {
		final Map<Object,Object> saveOptions=new HashMap<Object, Object>();
		saveOptions.put(Resource.OPTION_SAVE_ONLY_IF_CHANGED, Resource.OPTION_SAVE_ONLY_IF_CHANGED_MEMORY_BUFFER);
		IRunnableWithProgress operation=new IRunnableWithProgress() {
			public void run(IProgressMonitor monitor) throws InvocationTargetException,
					InterruptedException {
				ResourceSet resourceSet = editingDomain.getResourceSet();
				EList<Resource> resources = CDOUtil.getResources(resourceSet);
				monitor.beginTask("", resources.size()); //$NON-NLS-1$
				for(Resource resource:resources){
					saveOptions.put(CDOResource.OPTION_SAVE_PROGRESS_MONITOR, new SubProgressMonitor(monitor, 1));
					saveOptions.put(CDOResource.OPTION_SAVE_OVERRIDE_TRANSACTION, cdoView);
					try {
						resource.save(saveOptions);
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		};
		try {
			new ProgressMonitorDialog(getSite().getShell()).run(true, true, operation);
			((BasicCommandStack)editingDomain.getCommandStack()).saveIsDone();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void doSaveAs() {
		// TODO Auto-generated method stub
	}

	@Override
	public boolean isDirty() {
		return cdoView.isDirty();
	}

	@Override
	public boolean isSaveAsAllowed() {
		return false;
	}


	@Override
	public void addSelectionChangedListener(ISelectionChangedListener listener) {
		selectionChangedListeners.add(listener);
	}

	@Override
	public ISelection getSelection() {
		return editorSelection;
	}

	@Override
	public void removeSelectionChangedListener(
			ISelectionChangedListener listener) {
		selectionChangedListeners.remove(listener);
	}

	@Override
	public void setSelection(ISelection selection) {
		editorSelection = selection;

	    for (ISelectionChangedListener listener : selectionChangedListeners)
	    {
	      listener.selectionChanged(new SelectionChangedEvent(this, selection));
	    }
	}

	@Override
	public ITransactionEditingDomain getTransactionEditingDomain() {
		return editingDomain;
	}
	
	@Override
	public Viewer getViewer() {
		return treeViewer;
	}
	
	private void refreshTreeViewer() {
		getSite().getShell().getDisplay().asyncExec(new Runnable(){
			public void run() {
				treeViewer.refresh();
			}
		});
	}

	private void cdoViewDirtyStateChanged() {
		getSite().getShell().getDisplay().asyncExec(new Runnable(){
			public void run() {
				firePropertyChange(IEditorPart.PROP_DIRTY);
			}
		});
	}
	
	@Override
	public void setFocus() {
		if(treeViewer!=null&&!treeViewer.getControl().isDisposed()){
			treeViewer.getControl().setFocus();
		}
	}

	@Override
	public Object getAdapter(Class adapter) {
		if(adapter.equals(IPropertySheetPage.class)){
			return getPropertySheetPage();
		}else if(adapter.equals(ITransactionEditingDomain.class)){
			return editingDomain;
		}
		return super.getAdapter(adapter);
	}
	
	private Object getPropertySheetPage() {
		if(propertySheetPage==null||propertySheetPage.getControl().isDisposed()){
			propertySheetPage=new TabbedPropertySheetPage(this);
//			propertySheetPage=new ExtendedPropertySheetPage((TransactionAdapterFactoryEditingDomain)editingDomain){
//				@Override
//				protected void setSelectionToViewer(List<?> selection) {
//					EMFRabbitEditor.this.setSelectionToViewer(selection);
//					EMFRabbitEditor.this.setFocus();
//				}
//				
//				@Override
//				public void setActionBars(IActionBars actionBars) {
//					super.setActionBars(actionBars);
//					getActionBarContributor().shareGlobalActions(this, actionBars);
//				}
//			};
//			propertySheetPage.setPropertySourceProvider(new AdapterFactoryContentProvider(adapterFactory));
		}
		return propertySheetPage;
	}
	
	public void setSelectionToViewer(Collection<?> collection){
		final Collection<?> theSelection=collection;
		if(theSelection==null||theSelection.isEmpty()){
			return;
		}
		getSite().getShell().getDisplay().asyncExec(new Runnable(){
			public void run() {
				if(treeViewer!=null){
					treeViewer.setSelection(new StructuredSelection(theSelection.toArray()),true);
				}
			}
		});
	}

	@Override
	public void menuAboutToShow(IMenuManager manager) {
		((IMenuListener)getEditorSite().getActionBarContributor()).menuAboutToShow(manager);
	}
	
	public RabbitEditorActionBarContributor getActionBarContributor(){
		return (RabbitEditorActionBarContributor)getEditorSite().getActionBarContributor();
	}
	
	@Override
	public void dispose() {
		getSite().getPage().removePartListener(partListener);
		if(getActionBarContributor().getActiveEditor()==this){
			getActionBarContributor().setActiveEditor(null);
		}
		if(propertySheetPage!=null){
			propertySheetPage.dispose();
		}
		cdoView.removeListener(cdoViewListener);
		adapterFactory.dispose();
		cdoView.close();
		super.dispose();
	}

	@Override
	public String getContributorId() {
		return "org.karelun.rabbit.rcp.propertyContributor";
	}
}
